﻿using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using ESRI.ArcGIS.Client.Tasks;

namespace ESRI.ArcGIS.Client.Async
{
    internal static class TaskServices
    {
        internal static Task<TResult> HandleTask<TTask, TEventArgs, TResult>(
            this TTask task,
            Action<TTask> executeMethod,
            Action<TTask, EventHandler<TEventArgs>> subscribe,
            Action<TTask, EventHandler<TEventArgs>> unsubscribe,
            Func<TEventArgs, TResult> resultFunction,
            CancellationToken cancellationToken)
            where TEventArgs : TaskEventArgs
            where TTask : TaskBase
        {
            if (task == null) throw new ArgumentNullException("task");

            EventHandler<TaskFailedEventArgs> failedHandler = null;
            EventHandler<TEventArgs> completedHandler = null;
            var tcs = new TaskCompletionSource<TResult>();

            // ReSharper disable ImplicitlyCapturedClosure

            Action unregister = () =>
            {
                // ReSharper disable AccessToModifiedClosure
                unsubscribe(task, completedHandler);
                task.Failed -= failedHandler;
                // ReSharper restore AccessToModifiedClosure
            };

            completedHandler = (sender, e) =>
            {
                unregister();
                if (cancellationToken.IsCancellationRequested)
                    tcs.TrySetCanceled();
                else
                    tcs.TrySetResult(resultFunction(e));
                
            };

            failedHandler = (sender, e) =>
            {
                unregister();
                tcs.TrySetException(e.Error);
            };

            if (cancellationToken.CanBeCanceled)
            {
                cancellationToken.Register(() =>
                {
                    unregister();
                    task.CancelAsync();
                    tcs.TrySetCanceled();
                });
            }
            
            // ReSharper restore ImplicitlyCapturedClosure
            subscribe(task, completedHandler);
            task.Failed += failedHandler;
            try
            {
                executeMethod(task);
            }
            catch (Exception ex)
            {
                unregister();
                tcs.TrySetException(ex);
            }
            return tcs.Task;
        }

        internal static Task<TResult> HandleActionWithCallbackArgument<TResult>(Action<Action<TResult, Exception>> action)
        {
            var tcs = new TaskCompletionSource<TResult>();
            action((result, exception) =>
            {
                if (exception != null)
                    tcs.SetException(exception);
                else
                    tcs.SetResult(result);
            });
            return tcs.Task;
        }

        public static Task<TResult> HandleActionWithCallbackArgument<TInput, TResult>(Action<TInput, Action<TResult, Exception>> action, TInput arg1)
        {
            var tcs = new TaskCompletionSource<TResult>();
            action(arg1, (result, exception) =>
            {
                if (exception != null)
                    tcs.SetException(exception);
                else
                    tcs.SetResult(result);
            });
            return tcs.Task;
        }

        internal static Task<TResult> HandleAsyncOperation<TInstance, TEventArgs, TResult>(
            TInstance instance,
            Action<TInstance> executeMethod,
            Action<TInstance, EventHandler<TEventArgs>> subscribe,
            Action<TInstance, EventHandler<TEventArgs>> unsubscribe,
            Action<TInstance> cancelAction,
            Func<TEventArgs, TResult> resultFunction,
            CancellationToken cancellationToken)
            where TInstance : class
            where TEventArgs : AsyncCompletedEventArgs
        {
            if (instance == null) throw new ArgumentNullException("instance");

            EventHandler<TEventArgs> completedHandler = null;
            var tcs = new TaskCompletionSource<TResult>();

            // ReSharper disable ImplicitlyCapturedClosure

            Action unregister = () => unsubscribe(instance, completedHandler);

            completedHandler = (sender, e) =>
            {
                unregister();
                if (e.Error != null)
                {
                    tcs.TrySetException(e.Error);
                }
                else if (e.Cancelled || cancellationToken.IsCancellationRequested)
                {
                    tcs.TrySetCanceled();
                }
                else
                {
                    tcs.TrySetResult(resultFunction(e));
                }
            };

            if (cancellationToken.CanBeCanceled)
            {
                cancellationToken.Register(() =>
                {
                    unregister();
                    cancelAction(instance);
                    tcs.TrySetCanceled();
                });
            }

            // ReSharper restore ImplicitlyCapturedClosure
            subscribe(instance, completedHandler);
            try
            {
                executeMethod(instance);
            }
            catch (Exception ex)
            {
                unregister();
                tcs.TrySetException(ex);
            }
            return tcs.Task;
        }
    }
}